package com.tgm.service.impl;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tgm.domain.common.PageResult;
import com.tgm.domain.common.Result;
import com.tgm.domain.dto.*;
import com.tgm.domain.entity.ConsumptionRecord;
import com.tgm.domain.entity.MerchantInfo;
import com.tgm.domain.entity.UserInfo;
import com.tgm.domain.vo.*;
import com.tgm.mapper.ConsumptionRecordMapper;
import com.tgm.mapper.MerchantInfoMapper;
import com.tgm.mapper.UserInfoMapper;
import com.tgm.service.ConsumptionRecordService;
import com.tgm.service.MerchantInfoService;
import com.tgm.service.UserInfoService;
import com.tgm.utils.ConsumptionDataConverter;
import com.tgm.utils.PositionUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import static com.tgm.config.AIPromptConfig.GROUP_ANALYSIS_PROMPT;
import static com.tgm.config.AbnormalRuleConfig.*;

/**
 * 消费记录表(ConsumptionRecord)表服务实现类
 *
 * @author makejava
 * @since 2025-10-20 20:59:29
 */
@Service
public class ConsumptionRecordServiceImpl extends ServiceImpl<ConsumptionRecordMapper, ConsumptionRecord> implements ConsumptionRecordService {

    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private MerchantInfoMapper merchantInfoMapper;

    private final ChatClient chatClient;
    public ConsumptionRecordServiceImpl(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @Autowired
    private ConsumptionDataConverter consumptionDataConverter;

    /**
     * 查询消费记录
     * @param consumptionQueryDTO
     * @return
     */
    @Override
    public PageResult<ConsumptionShowVO> queryByPage(ConsumptionQueryDTO consumptionQueryDTO) {
        // 分页
        PageHelper.startPage(consumptionQueryDTO.getPage(), consumptionQueryDTO.getPageSize());
        // 查询 查三张表 消费记录表 用户表 商户表
        List<ConsumptionShowVO> list = baseMapper.queryByPage(consumptionQueryDTO);
        // 封装分页结果
        Page<ConsumptionShowVO> p = (Page<ConsumptionShowVO>) list;

        return new PageResult<>(p.getTotal(),p.getResult());
    }


    /**
     * 统计消费趋势（折线图）
     * @param statisticsTrendDTO
     * @return
     */
    @Override
    @Transactional
    public StatisticsVO statisticsTrend(StatisticsTrendDTO statisticsTrendDTO) {
        System.out.println("开始执行 statisticsTrend 查询，参数：" + statisticsTrendDTO);
        List<Map<String,Object>> list = baseMapper.statisticsTrend(statisticsTrendDTO);

        // 创建12个月的默认值
        List<Object> amountByMonth = list.stream().map(item->item.get("amount")).toList();
        List<Object> countByMonth = list.stream().map(item->item.get("count")).toList();

        return new StatisticsVO(amountByMonth, countByMonth);
    }

    /**
     * 统计消费类型饼图
     * @param statisticsPieDTO
     * @return
     */
    @Override
    public StatisticsVO statisticsPie(StatisticsPieDTO statisticsPieDTO) {
        List<Map<String,Object>> list = baseMapper.statisticsPie(statisticsPieDTO);
        // amountByMerchantType [{'name': ’merchantType‘,'value' : 'amount'}]
        List<Map<String,Object>> amountByMerchantType =  list.stream().map(item->{
            Map<String,Object> map = new HashMap<>();
            map.put("name", item.get("merchantType"));
            map.put("value", item.get("amount"));
            return map;
        }).toList();
        List<Map<String,Object>> countByMerchantType =  list.stream().map(item->{
            Map<String,Object> map = new HashMap<>();
            map.put("name", item.get("merchantType"));
            map.put("value", item.get("count"));
            return map;
        }).toList();

        return new StatisticsVO(amountByMerchantType, countByMerchantType);
    }

    /**
     * 统计商户热度
     * @param heatMapDTO
     * @return
     */
    @Override
    public List<HeatMapVO> statHeatMapData(HeatMapDTO heatMapDTO) {
        return baseMapper.statHeatMapData(heatMapDTO);
    }

    /**
     * 多线程导入消费记录
     * @param file
     * @return
     * @throws IOException
     */
    @Override
    public Result importData(MultipartFile file) throws IOException {
        //解析excel,使用easyexcel
        //表头：用户类型user_type，用户名user_name，学号/工号user_id，学院department，年级grade，
        // 商户类型merchant_type，商户名称merchant_name，商户纬度longitude，商户经度latitude，
        // 消费时间consume_time，消费金额amount
        //创建临时文件
        File tempFile = File.createTempFile("temp", ".xlsx");
        file.transferTo(tempFile);
        // 保存外部类引用
        ConsumptionRecordServiceImpl outer = this;

        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        List<Future<?>> futures = new ArrayList<>();

        try {
            EasyExcel.read(tempFile, ImportConsumptionDTO.class, new ReadListener<ImportConsumptionDTO>() {

                public static final int BATCH_COUNT = 10000;//一次导入10000条数据到数据库中

                //创建缓存列表
                List<ImportConsumptionDTO> cacheList = new ArrayList<>(BATCH_COUNT);

                @Override
                public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
                    System.out.println("异常：" + e.getMessage());
                    throw new RuntimeException(e);
                }

                @Override
                public void invoke(ImportConsumptionDTO data, AnalysisContext analysisContext) {
                    if (data.getIsAbnormal() == null) {
                        data.setIsAbnormal(0); // 默认设为正常
                        data.setAbnormalReason(""); // 默认设为空字符串而不是null
                    }
                    //判断当前消费记录是否异常
                    //判断依据：单次消费金额是否超过阈值？
                    BigDecimal amount = data.getAmount();
                    if (amount != null && amount.compareTo(LARGE_AMOUNT_THRESHOLD) > 0) {
                        appendAbnormalReason(data, LARGE_AMOUNT_REASON);
                    }
                    //本次消费地点是否超出范围？根据经纬度计算距离中心点，如果超过5000则认为超出范围
                    //获取中心点经纬度
                    double distance = PositionUtil.calculateDistance(data.getLatitude(), data.getLongitude(),
                            CENTER_LATITUDE, CENTER_LONGITUDE);
                    if(distance > DISTANCE_THRESHOLD){
                        appendAbnormalReason(data, DISTANCE_REASON);
                    }
                    //消费时间是否正常？
                    Date consumeTime = data.getConsumeTime();
                    if (consumeTime != null){
                        LocalTime consumelocalTime = consumeTime.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
                        if (consumelocalTime.isAfter(NIGHT_CONSUME_START) || consumelocalTime.isBefore(NIGHT_CONSUME_END)) {
                            appendAbnormalReason(data, NIGHT_CONSUME_REASON);
                        }
                    }
                    cacheList.add(data);
                    if (cacheList.size() >= BATCH_COUNT) {
                        // 提交任务到线程池
                        List<ImportConsumptionDTO> batchToProcess = new ArrayList<>(cacheList);
                        Future<?> future = executorService.submit(() -> {
                            saveDataBatch(batchToProcess);
                        });
                        futures.add(future);
                        cacheList.clear(); //清空缓存 一定要
                    }
                }


                private static void appendAbnormalReason(ImportConsumptionDTO data, String largeAmountReason) {
                    if (data.getIsAbnormal() != null && data.getIsAbnormal() == 1) {
                        data.setAbnormalReason((data.getAbnormalReason() + "，" + largeAmountReason));
                    } else {
                        data.setIsAbnormal(1);
                        data.setAbnormalReason(largeAmountReason);
                    }
                }

                Map<String, MerchantInfo> merchantInfoMap = new ConcurrentHashMap<>();
                Map<String,UserInfo> userInfoMap = new ConcurrentHashMap<>();

                private void saveDataBatch(List<ImportConsumptionDTO> cacheList) {
                    List<UserInfo> userInfos = new ArrayList<>();
                    List<MerchantInfo> merchantInfos = new ArrayList<>();
                    List<ConsumptionRecord> consumptionRecords = new ArrayList<>();

                    for (ImportConsumptionDTO data : cacheList) {
                        //用户信息 基于UserId确认唯一
                        UserInfo userInfo1 = userInfoMap.computeIfAbsent(data.getUserId(), key -> {
                            UserInfo userInfo = new UserInfo();
                            userInfo.setUserId(data.getUserId());
                            userInfo.setUserName(data.getUserName());
                            userInfo.setDepartment(data.getDepartment());
                            userInfo.setGrade(data.getGrade());
                            userInfo.setUserType(data.getUserType().equals("学生") ? 0 : 1);
                            userInfo.setCreateTime(LocalDateTime.now());
                            return userInfo;
                        });
                        userInfos.add(userInfo1);

                        //商户信息 - 基于商户名称和类型确定唯一性
                        String merchantKey = data.getMerchantName() + "_" + data.getMerchantType();
                        MerchantInfo merchantInfo = merchantInfoMap.computeIfAbsent(merchantKey, key -> {
                            MerchantInfo newMerchant = new MerchantInfo();
                            String merchantId = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
                            newMerchant.setMerchantId(merchantId);
                            newMerchant.setMerchantName(data.getMerchantName());
                            newMerchant.setMerchantType(data.getMerchantType());
                            newMerchant.setLongitude(BigDecimal.valueOf(data.getLongitude()));
                            newMerchant.setLatitude(BigDecimal.valueOf(data.getLatitude()));
                            return newMerchant;
                        });
                        merchantInfos.add(merchantInfo);

                        //消费记录
                        ConsumptionRecord consumptionRecord = new ConsumptionRecord();
                        consumptionRecord.setUserId(data.getUserId());
                        consumptionRecord.setMerchantId(merchantInfo.getMerchantId());
                        consumptionRecord.setConsumeTime(data.getConsumeTime());
                        consumptionRecord.setAmount(data.getAmount());
                        consumptionRecord.setIsAbnormal(data.getIsAbnormal());
                        consumptionRecord.setAbnormalReason(data.getAbnormalReason());
                        consumptionRecords.add(consumptionRecord);
                    }



                    outer.userInfoMapper.insertBatch(userInfos);
                    outer.merchantInfoMapper.insertBatch(merchantInfos);
                    outer.baseMapper.insertBatch(consumptionRecords);
                }

                @Override
                public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    //处理剩余数据
                    if(!cacheList.isEmpty()){
                        saveDataBatch(cacheList);
                    }

                    // 等待所有任务完成
                    for (Future<?> future : futures) {
                        try {
                            future.get();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    // 关闭线程池
                    executorService.shutdown();
                    System.out.println("处理完成");
                }

                @Override
                public boolean hasNext(AnalysisContext analysisContext) {
                    return true;
                }
            }).sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
            // 关闭线程池
            executorService.shutdown();
            return Result.error("导入出错："+e.getMessage());
        } finally {
            tempFile.delete();
        }

        return Result.success();
    }

    /*
    单线程导入数据
     */
/*    @Override
    @Transactional
    public Result importData(MultipartFile file) throws IOException {
        //解析excel,使用easyexcel
        //表头：用户类型user_type，用户名user_name，学号/工号user_id，学院department，年级grade，
        // 商户类型merchant_type，商户名称merchant_name，商户纬度longitude，商户经度latitude，
        // 消费时间consume_time，消费金额amount
        //创建临时文件
        File tempFile = File.createTempFile("temp", ".xlsx");
        file.transferTo(tempFile);
        // 保存外部类引用
        ConsumptionRecordServiceImpl outer = this;


        try {
            EasyExcel.read(tempFile, ImportConsumptionDTO.class, new ReadListener<ImportConsumptionDTO>() {

                public static final int BATCH_COUNT = 5000;//一次导入1000条数据到数据库中

                //创建缓存列表
                List<ImportConsumptionDTO> cacheList = new ArrayList<>(BATCH_COUNT);

                @Override
                public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
                    System.out.println("异常：" + e.getMessage());
                    throw new RuntimeException(e);
                }

                @Override
                public void invoke(ImportConsumptionDTO data, AnalysisContext analysisContext) {
                    if (data.getIsAbnormal() == null) {
                        data.setIsAbnormal(0); // 默认设为正常
                        data.setAbnormalReason(""); // 默认设为空字符串而不是null
                    }
                    //判断当前消费记录是否异常
                    //判断依据：单次消费金额是否超过阈值？
                    BigDecimal amount = data.getAmount();
                    if (amount != null && amount.compareTo(LARGE_AMOUNT_THRESHOLD) > 0) {
                        appendAbnormalReason(data, LARGE_AMOUNT_REASON);
                    }
                    //本次消费地点是否超出范围？根据经纬度计算距离中心点，如果超过5000则认为超出范围
                    //获取中心点经纬度
                    double distance = PositionUtil.calculateDistance(data.getLatitude(), data.getLongitude(),
                            CENTER_LATITUDE, CENTER_LONGITUDE);
                    if(distance > DISTANCE_THRESHOLD){
                        appendAbnormalReason(data, DISTANCE_REASON);
                    }
                    //消费时间是否正常？
                    Date consumeTime = data.getConsumeTime();
                    if (consumeTime != null){
                        LocalTime consumelocalTime = consumeTime.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
                        if (consumelocalTime.isAfter(NIGHT_CONSUME_START) || consumelocalTime.isBefore(NIGHT_CONSUME_END)) {
                            appendAbnormalReason(data, NIGHT_CONSUME_REASON);
                        }
                    }
                    cacheList.add(data);
                    if (cacheList.size() >= BATCH_COUNT) {
                        // 提交任务到线程池
                        saveDataBatch(cacheList);
                        cacheList.clear(); //清空缓存 一定要
                    }
                }

                private static void appendAbnormalReason(ImportConsumptionDTO data, String largeAmountReason) {
                    if (data.getIsAbnormal() != null && data.getIsAbnormal() == 1) {
                        data.setAbnormalReason((data.getAbnormalReason() + "，" + largeAmountReason));
                    } else {
                        data.setIsAbnormal(1);
                        data.setAbnormalReason(largeAmountReason);
                    }
                }

                Map<String, MerchantInfo> merchantInfoMap = new HashMap<>();
                Map<String,UserInfo> userInfoMap = new ConcurrentHashMap<>();

                private void saveDataBatch(List<ImportConsumptionDTO> cacheList) {
                    List<UserInfo> userInfos = new ArrayList<>();
                    List<ConsumptionRecord> consumptionRecords = new ArrayList<>();
                    for (ImportConsumptionDTO data : cacheList) {
                        //用户信息 基于UserId确认唯一
                        UserInfo userInfo1 = userInfoMap.computeIfAbsent(data.getUserId(), key -> {
                            UserInfo userInfo = new UserInfo();
                            userInfo.setUserId(data.getUserId());
                            userInfo.setUserName(data.getUserName());
                            userInfo.setDepartment(data.getDepartment());
                            userInfo.setGrade(data.getGrade());
                            userInfo.setUserType(data.getUserType().equals("学生") ? 0 : 1);
                            userInfo.setCreateTime(LocalDateTime.now());
                            return userInfo;
                        });
                        userInfos.add(userInfo1);
                        //商户信息 - 基于商户名称和类型确定唯一性
                        MerchantInfo merchantInfo;

                        String merchantKey = data.getMerchantName() + "_" + data.getMerchantType();
                        if (!merchantInfoMap.containsKey(merchantKey)) {
                            merchantInfo = new MerchantInfo();
                            String merchantId = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
                            merchantInfo.setMerchantId(merchantId);
                            merchantInfo.setMerchantName(data.getMerchantName());
                            merchantInfo.setMerchantType(data.getMerchantType());
                            merchantInfo.setLongitude(BigDecimal.valueOf(data.getLongitude()));
                            merchantInfo.setLatitude(BigDecimal.valueOf(data.getLatitude()));
                            merchantInfoMap.put(merchantKey, merchantInfo);
                        } else {
                            merchantInfo = merchantInfoMap.get(merchantKey);
                        }
                        //消费记录
                        ConsumptionRecord consumptionRecord = new ConsumptionRecord();
                        consumptionRecord.setUserId(data.getUserId());
                        consumptionRecord.setMerchantId(merchantInfo.getMerchantId());
                        consumptionRecord.setConsumeTime(data.getConsumeTime());
                        consumptionRecord.setAmount(data.getAmount());
                        consumptionRecord.setIsAbnormal(data.getIsAbnormal());
                        consumptionRecord.setAbnormalReason(data.getAbnormalReason());
                        consumptionRecords.add(consumptionRecord);
                    }
                    outer.userInfoMapper.insertBatch(userInfos);
                    outer.merchantInfoMapper.insertBatch(new ArrayList<>(merchantInfoMap.values()));
                    outer.baseMapper.insertBatch(consumptionRecords);
                }

                @Override
                public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    //处理剩余数据
                    if(!cacheList.isEmpty()){
                        saveDataBatch(cacheList);
                    }
                    System.out.println("处理完成");
                }

                @Override
                public boolean hasNext(AnalysisContext analysisContext) {
                    return true;
                }
            }).sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("导入出错："+e.getMessage());
        } finally {
            tempFile.delete();
        }

        return Result.success();
    }*/




    /**
     * 按条件导出消费数据到Excel
     */
    public void exportConsumptionExcel(ExcelExportQuery query, HttpServletResponse response) throws IOException {
        // 1. 按筛选条件查询原始数据
        List<PortraitVO> originalData = baseMapper.selectByConditions(
                query.getDepartment(),
                query.getGrade()
        );

        // 2. 转换为Excel导出模型（格式化数据）
        List<ConsumptionExcelVO> excelData = originalData.stream().map(vo -> {
            ConsumptionExcelVO excelVO = new ConsumptionExcelVO();
            excelVO.setConsumeTime(vo.getConsumeTime());
            excelVO.setDepartment(vo.getDepartment());
            excelVO.setGrade(vo.getGrade());
            excelVO.setMerchantType(vo.getMerchantType());
            excelVO.setMerchantName(vo.getMerchantName());
            excelVO.setAmount(vo.getAmount());
            // 转换异常标记（假设abnormalReason不为空即为异常）
            if(vo.getAbnormalReason() != null && !vo.getAbnormalReason().isEmpty()){
                excelVO.setAbnormalReason(vo.getAbnormalReason());
                excelVO.setIsAbnormal("是");
            }else {excelVO.setIsAbnormal("否");}
            return excelVO;
        }).collect(Collectors.toList());

        //对于原始数据调用AI进行分析，得到的分析结果存在单独一列的“分析结果中”
        // 3. 调用AI生成群体特征（提示词明确要求标签和核心特征）
        String groupConsumeFeaturesText = consumptionDataConverter.convertUserConsumptionToText(query.getDepartment(), query.getGrade(), originalData);

        String prompt = GROUP_ANALYSIS_PROMPT.formatted(groupConsumeFeaturesText);

        String content = chatClient.prompt()
                .user(prompt)
                .call()
                .content();

        // 3. 创建分析结果行（仅填充“分析结果”列，其他列为空）
        ConsumptionExcelVO analysisRow = new ConsumptionExcelVO();
        analysisRow.setAnalysisResult(content); // 仅这一列有值，其他列默认null（Excel中显示为空）

// 4. 构建最终导出列表：表头行 → 分析结果行 → 原始数据行
        List<ConsumptionExcelVO> finalExcelData = new ArrayList<>();
// 先添加分析结果行（后续会成为第二行，因为EasyExcel会自动添加表头行作为第一行）
        finalExcelData.add(analysisRow);
// 再添加所有原始数据行（从第三行开始）
        finalExcelData.addAll(excelData);

        // 5. 设置响应头（告诉浏览器下载文件）
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        // 文件名编码（避免中文乱码）
        String fileName = URLEncoder.encode("消费数据报表_" + LocalDateTime.now(), StandardCharsets.UTF_8.name())
                .replaceAll("\\+", "%20"); // 替换空格为%20
        response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + fileName + ".xlsx");

        // 6. 用EasyExcel写入响应流（直接下载）
        EasyExcel.write(response.getOutputStream(), ConsumptionExcelVO.class)
                .sheet("消费记录") // Excel sheet名称
                .doWrite(finalExcelData);
    }

}